package cvut.swe.semestralka.sesame;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.sail.nativerdf.NativeStore;
import org.springframework.beans.factory.annotation.Value;

import cvut.swe.semestralka.controller.SearchForm;

public class SesameDao {
	
	private final Logger logger = Logger.getLogger(SesameDao.class);
	
	private String fileLocation = "";
	
	private final String DEPART_URI = "http://www.swe.cz/depart/";
	private final String TIMETABLE_URI = "http://www.swe.cz/timetable/";
	private final String LINE_URI = "http://www.swe.cz/line/";
	private final String STATION_URI = "http://www.swe.cz/station/";
	
	private final String URI_TYPE_STATION="http://www.swe.cz/ontology/Station";
	private final String URI_TYPE_DEPART="http://www.swe.cz/ontology/Depart";
	private final String URI_TYPE_LINE="http://www.swe.cz/ontology/Line";
	private final String URI_TYPE_TIMETABLE="http://www.swe.cz/ontology/Timetable";
	
	private final String URI_hasDepartures="http://www.swe.cz/ontology/hasDepartures";
	private final String URI_hasStations="http://www.swe.cz/ontology/hasStation";
	private final String URI_hasline="http://www.swe.cz/ontology/hasLine";
	private final String URI_haslast="http://www.swe.cz/ontology/hasLast";
	
	private final String URI_hour="http://www.swe.cz/ontology/hour";
	private final String URI_minute="http://www.swe.cz/ontology/minute";
	private final String URI_name="http://www.swe.cz/ontology/name";
	private final String URI_poradi="http://www.swe.cz/ontology/poradi";
	private final String URI_number="http://www.swe.cz/ontology/number";
	private final String URI_type="http://www.swe.cz/ontology/type";
	
	private URI stationTypeUri;
	private URI departTypeUri;
	private URI lineTypeUri;
	private URI timetableTypeUri;
	
	private URI hasDepartures;
	private URI hasStations;
	private URI hasline;
	private URI hasLast;
	
	private URI hour;
	private URI minute;
	private URI name;
	private URI poradi; // unused
	private URI number;
	private URI type;
	
	private final String QUERY_PREFIX ="PREFIX depart: <http://www.swe.cz/depart/> \n"+
										"PREFIX timetable: <http://www.swe.cz/timetable/> \n"+
										"PREFIX line: <http://www.swe.cz/line/> \n"+
										"PREFIX station: <http://www.swe.cz/station/> \n"+	
										"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n"+
										"PREFIX on: <http://www.swe.cz/ontology/> \n";
	
	private RepositoryConnection conn = null;
	private ValueFactory f;
	
	
	public SesameDao(){
		
	}
	
	@Value("${rdf.fileLocation}") 
	public void setFileLocation(String fileLocation) {
		this.fileLocation = fileLocation;
	}

	public String getFileLocation() {
		return fileLocation;
	}

	public URI createStation(String stName) throws RepositoryException{
		URI station = addStatement(STATION_URI, stName, stationTypeUri);
		Literal litName = f.createLiteral(stName);
		conn.add(station, name, litName);
		return station;
	} 
	
	public URI createLine(String lineNumber) throws RepositoryException{
		 URI line = addStatement(LINE_URI, lineNumber, lineTypeUri);
		 Literal literalLineNumber = f.createLiteral(lineNumber);
		 conn.add(line,number,literalLineNumber );
		 return line;
	}
	
	public URI createTimetable(String stationName, String line, String smer){
		String name = stationName+"-"+line+"-"+smer;
		URI timetable = addStatement(TIMETABLE_URI, name, timetableTypeUri);
		
		return timetable;
	}
	
	public URI createDepart(int hour, int minute,String stationName, String line) throws RepositoryException{
		String name = stationName+"-"+line+"-"+hour+"-"+minute;
		URI depart = addStatement(DEPART_URI, name, departTypeUri);
		Literal hourLiteral = f.createLiteral(hour);
		Literal minuteLiteral = f.createLiteral(minute);
		conn.add(depart,this.hour, hourLiteral);
		conn.add(depart,this.minute, minuteLiteral);
		return depart;
	}
	
	public boolean addLinkToTimetable(URI timetableUri, URI lineUri) throws RepositoryException{
		try{
			conn.add(timetableUri, hasline, lineUri);
		}catch(Exception ex){
			logger.error("chyba vlozeni line do timetable:", ex);
			return false;
		}
		return true;
	}
	
	public boolean addStationToTimetable(URI timetableUri, URI stationUri){
		try{
			conn.add(timetableUri,hasStations,stationUri);
		}catch(Exception ex){
			logger.error("chyba vlozeni statiion do timetable:"+ stationUri.stringValue(), ex);
			return false;
		}
		return true;
	}
	
	public boolean addDepartToTimeTable(URI timetableUri, URI departUri){
		try{
			conn.add(timetableUri,hasDepartures,departUri);
		}catch(Exception ex){
			logger.error("chyba vlozeni depart do timetable:"+ departUri.stringValue(), ex);
			return false;
		}
		return true;
	}
	
	public boolean addStationToTimeTableAsLastStation(URI timetableUri, URI stationUri){
		try{
			conn.add(timetableUri,hasLast,stationUri);
		}catch(Exception ex){
			logger.error("chyba vlozeni statiion do timetable jako posledni stanice:"+ stationUri.stringValue(), ex);
			return false;
		}
		return true;
	}
	
	public List<String> getAvailableLinks() throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		List<String> links = new ArrayList<String>();
		String queryString = QUERY_PREFIX + " SELECT ?b WHERE { ?a rdf:type on:Line; on:number ?b. }";
	      TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
	      TupleQueryResult res = tupleQuery.evaluate();
	      
	      while (res.hasNext()) {
	          BindingSet bindingSet = res.next();
	          org.openrdf.model.Value name1 = bindingSet.getValue("b");
	          links.add(name1.stringValue());
	      } 
		
		return links;
	}
	
	public List<String> getAvailableStations() throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		List<String> links = new ArrayList<String>();
		String queryString = QUERY_PREFIX + " SELECT ?b WHERE { ?a rdf:type on:Station; on:name ?b. }";
        TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        TupleQueryResult res = tupleQuery.evaluate();
      
        while (res.hasNext()) {
            BindingSet bindingSet = res.next();
            org.openrdf.model.Value name1 = bindingSet.getValue("b");
            links.add(name1.stringValue());
        } 
		
		return links;
		
	}
	
	public List<String> getLinksByStation(String station) throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		List<String> links = new ArrayList<String>();
		String queryString = QUERY_PREFIX + " SELECT ?a WHERE { ?a rdf:type on:Link; on:hasLine station:"+station+". }";
        TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        TupleQueryResult res = tupleQuery.evaluate();
      
        while (res.hasNext()) {
            BindingSet bindingSet = res.next();
            org.openrdf.model.Value name1 = bindingSet.getValue("a");
            links.add(name1.stringValue());
        } 
		return links;
	}
	
	public boolean isStationInLink(String link, String station, String smer) throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		
		String queryString = QUERY_PREFIX + " SELECT ?a WHERE { ?a rdf:type on:Timetable; on:hasLast [ on:name '"+smer+"']; on:hasStation [ on:name '"+station+"' ]; on:hasLine [ on:number '"+link+"' ] . }";
        TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        TupleQueryResult res = tupleQuery.evaluate();
      
        return res.hasNext();
        
	}
	
	public List<String> getLastStationsForLink(String link) throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		List<String> links = new ArrayList<String>();
		String queryString = QUERY_PREFIX + " SELECT ?z WHERE { ?a rdf:type on:Timetable; on:hasLast ?w; on:hasLine [ on:number '"+link+"' ] . ?w on:name ?z}";
        TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        TupleQueryResult res = tupleQuery.evaluate();
        while (res.hasNext()) {
            BindingSet bindingSet = res.next();
            org.openrdf.model.Value name1 = bindingSet.getValue("z");
            links.add(name1.stringValue());
        } 
		return links;
	}
	
	public List<String> getTimetableForLinkInStation(String station, String link, String smer) throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		List<String> links = new ArrayList<String>();
		String queryString = QUERY_PREFIX + " SELECT ?a WHERE { ?a rdf:type on:Timetable; on:hasStation station:"+station+" [ on:hasLine line:"+link+"]. }";
        TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        TupleQueryResult res = tupleQuery.evaluate();
      
        while (res.hasNext()) {
            BindingSet bindingSet = res.next();
            org.openrdf.model.Value name1 = bindingSet.getValue("a");
            links.add(name1.stringValue());
        } 
		
		return links;
		
	}
	
	public List<String> getDepart(String station, String link, String hour, String minute) throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		List<String> links = new ArrayList<String>();
		String queryString = QUERY_PREFIX + " SELECT ?a WHERE { ?a rdf:type on:Depart; on:hasTimetable [ on:hasLine link:"+link+"; on:hasStation station:"+station+"]. }"; // profiltrovani pokud mozno
        TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        TupleQueryResult res = tupleQuery.evaluate();
        
        while (res.hasNext()) {
            BindingSet bindingSet = res.next();
            org.openrdf.model.Value name1 = bindingSet.getValue("a");
            links.add(name1.stringValue());
        } 
		
		return links;
	}
	
	public Timetable findsDeparts(SearchForm form) throws RepositoryException, MalformedQueryException, QueryEvaluationException{
		Station st = new Station();
		String queryStationString = QUERY_PREFIX + " SELECT ?a WHERE { ?a rdf:type on:Station; on:name '"+form.getFromStation()+"'.}";
		TupleQuery tupleQuery1 = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryStationString);
        TupleQueryResult res1 = tupleQuery1.evaluate();
		st.setStation(form.getFromStation());
        if(res1.hasNext()){
        	BindingSet bindingSet = res1.next();
            org.openrdf.model.Value v1 = bindingSet.getValue("a");
            st.setUri(v1.stringValue());
        }
        
        
        Line line = new Line();
		String querylineString = QUERY_PREFIX + " SELECT ?a WHERE { ?a rdf:type on:Line; on:number '"+form.getBusLine()+"'.}";
		TupleQuery tupleQuery2 = conn.prepareTupleQuery(QueryLanguage.SPARQL, querylineString);
        TupleQueryResult res2 = tupleQuery2.evaluate();
		line.setNumber(form.getBusLine());
        if(res2.hasNext()){
        	BindingSet bindingSet = res2.next();
            org.openrdf.model.Value v1 = bindingSet.getValue("a");
            line.setUri(v1.stringValue());
        }
		 
		String queryString = QUERY_PREFIX + " SELECT ?a ?m ?n ?w WHERE { ?a rdf:type on:Timetable; on:hasLast [ on:name '"+form.getLastStation()+"']; on:hasLine [ on:number '"+form.getBusLine()+"']; on:hasStation [ on:name '"+form.getFromStation()+"']; on:hasDepartures ?w . ?w on:hour ?m; on:minute ?n FILTER( ?m = "+form.getHour()+" && ?n >= "+form.getMinute()+") . }"; // profiltrovani pokud mozno
        TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        TupleQueryResult res = tupleQuery.evaluate();
        Timetable tm = new Timetable();
        tm.setLine(line);
        tm.setStation(st);
        while(res.hasNext()){
        	BindingSet bindingSet = res.next();
            org.openrdf.model.Value v1 = bindingSet.getValue("a");
            org.openrdf.model.Value v2 = bindingSet.getValue("w");
            org.openrdf.model.Value v3 = bindingSet.getValue("m");
            org.openrdf.model.Value v4 = bindingSet.getValue("n");
            Integer hour =Integer.valueOf(v3.stringValue());
            Integer minute = Integer.valueOf(v4.stringValue());
            Depart depart = new Depart();
            depart.setHour(hour);
            depart.setMinute(minute);
            depart.setDepartUri(v2.stringValue());
            tm.setTimetableURI(v1.stringValue());
            tm.addDepart(hour, depart);
        }
        queryString = QUERY_PREFIX + " SELECT ?a ?m ?n ?w WHERE { ?a rdf:type on:Timetable; on:hasLast [ on:name '"+form.getLastStation()+"'];on:hasLine [ on:number '"+form.getBusLine()+"']; on:hasStation [ on:name '"+form.getFromStation()+"']; on:hasDepartures ?w . ?w on:hour ?m; on:minute ?n FILTER( ?m > "+form.getHour()+") . }"; // profiltrovani pokud mozno
        tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
        res = tupleQuery.evaluate();
        while(res.hasNext()){
        	BindingSet bindingSet = res.next();
            org.openrdf.model.Value v1 = bindingSet.getValue("a");
            org.openrdf.model.Value v2 = bindingSet.getValue("w");
            org.openrdf.model.Value v3 = bindingSet.getValue("m");
            org.openrdf.model.Value v4 = bindingSet.getValue("n");
            Integer hour =Integer.valueOf(v3.stringValue());
            Integer minute = Integer.valueOf(v4.stringValue());
            Depart depart = new Depart();
            depart.setHour(hour);
            depart.setMinute(minute);
            depart.setDepartUri(v2.stringValue());
            tm.setTimetableURI(v1.stringValue());
            tm.addDepart(hour, depart);
        }
		return tm;
	}
	
	private URI addStatement(String uriOfsubject, String name, URI URI_TYPE){
		String uriTemp = uriOfsubject+name;
		URI uri = f.createURI(uriTemp);
		try {
			conn.add(uri, RDF.TYPE, URI_TYPE);
		} catch (RepositoryException e) {
			e.printStackTrace();
			return null;
		}
		return uri;
	} 
	
	/**
	 * inits rdf
	 * @throws RepositoryException 
	 */
	public void init() throws RepositoryException{
		File dataDir = new File(fileLocation);
		String indexes = "spoc,posc,cosp";
		Repository myRepository = new SailRepository(new NativeStore(dataDir, indexes));
		myRepository.initialize();
		conn = myRepository.getConnection();
		f = myRepository.getValueFactory();
		
		//inicialize ontologies
		stationTypeUri = f.createURI(URI_TYPE_STATION);
		departTypeUri = f.createURI(URI_TYPE_DEPART);
		lineTypeUri = f.createURI(URI_TYPE_LINE);
		timetableTypeUri = f.createURI(URI_TYPE_TIMETABLE);
		
		hasDepartures = f.createURI(URI_hasDepartures);
		hasStations= f.createURI(URI_hasStations );
		hasline= f.createURI(URI_hasline);
		hasLast= f.createURI(URI_haslast);
		
		conn.add(hasDepartures, RDF.LIST,departTypeUri);
		
		hour= f.createURI(URI_hour);
		minute= f.createURI(URI_minute );
		name= f.createURI(URI_name);
		poradi= f.createURI(URI_poradi);
		number= f.createURI(URI_number);
		type= f.createURI(URI_type);
		
	}
	
	/**
	 * destroyes model and saves themto file
	 * @throws RepositoryException 
	 */
	public void destroy() throws RepositoryException{
		conn.close();
	}

}
